Amazon DynamoDBによるTomcatセッション永続化とフェイルオーバー
Tomcatのセッション管理
Tomcatでクラスター構成にする場合、課題となるのがセッション管理です。ロードバランサーでセッションIDを保持することで、毎回同じサーバーにリクエストが向かうのであれば問題なさそうに見えますが、あるサーバーがダウンしてしまうとセッション情報が消えてしまいます。これを解決する方法として、データベースにセッション情報を保持する方法が一般的ですが、データベースへ負荷が掛かりますし、データベースが落ちたら困ります。何かもっと良い方法は無いかと皆さん思っていたはずです。そこで、AWSですよねー。AWSでは、ElastiCacheやDynamoDBがサービスとして提供されています。ここで、永続化をしっかりやってくれるのはDynamoDBであり、AWS SDK for Javaでの登場が待たれていたわけです。そして、このたび出てきました!
スティッキーセッション
ロードバランサーがセッションIDをキーにしてリクエストするべきインスタンスを振り分けます。方式としては、アプリケーションサーバー発行のIDまたは、ロードバランサー発行のIDを用います。Tomcatであればjsessionidが使われますね。
非スティッキーセッション
クライアントからリクエストする度に同じインスタンスに向かいません。ここではラウンドロビンと言いたいところですが、厳密には単純な分散ではないため"非"としました。
【ELB】負荷分散とEC2へのリクエスト数との関係について調べてみた
なぜDynamoDBがいいの?
ここでは、なぜDynamoDBがセッション管理に良いのか(このあと前提が覆りますがこのままお楽しみくださいw)確認したいと思います。まずは、非スティッキーセッションの場合のセッションの考え方を図にしました。毎回どのサーバーにリクエストが向かうか分かりませんから、セッション管理を行うことはできません。
ロードバランサー配下のインスタンスでセッション管理を行うために、スティッキーセッションを行った場合の図が以下です。この方法のリスクとしては、セッション管理しているサーバーに障害が起こった場合、セッション情報が消えてしまうことです。セッション情報は主にログイン情報や買い物かごの中身など一時的に持っておくデータですから最悪消えてしまっても最初から処理をすれば大きな問題にはなりませんが、利用者としては買い物かごの中身が消えてしまうことは良い気分ではありません。
ということで、次に考えるのは、各WEBサーバーにセッション情報を置かずにデータベースを利用して保存しておくことです。これによって、EC2で構築したWEBサーバーに障害が発生してもセッション情報が消えることはありません。また、スケールアウト/スケールインの際にも有効です。良いことづくしですが、リスクが無いわけではありません。データベースサーバーに障害が発生した場合はセッション情報が復帰するまで全体に影響が出てしまいます。RDSであれば、レプリケーションからフェイルオーバーして復帰しますが、WEBサーバーがスケールアウトした際にデータベースへの接続数が倍々で増えてしまって負荷になってしまうかもしれませんね。
ということで、もっと良い方法ないかなーということで、ElastiCacheではなくDynamoDBです。ElastiCacheはインメモリで高速に動きますので、セッション管理には最適なような気がしますが、永続的なデータを扱うモノではありませんので悩みます。「高速」「永続化」「クラスタ」「サービス」「Tomcatで指定できる」を兼ね備えたもの・・・・DynamoDBです!
DynamoDB Session Managerのセットアップ
それでは、Tomcatとセッション管理のマネージャをセットアップしてみましょう。
$ sudo yum update -y $ sudo yum install tomcat7* $ wget http://sdk-for-java.amazonwebservices.com/latest/aws-java-sdk.zip $ unzip aws-java-sdk.zip $ cd aws-java-sdk-1.5.7/lib/ $ sudo cp aws-java-sdk-1.5.7.jar /usr/share/tomcat7/lib/ $ wget https://github.com/aws/aws-dynamodb-session-tomcat/releases/download/v1.0.0/AmazonDynamoDBSessionManagerForTomcat-1.0.0.jar $ sudo cp AmazonDynamoDBSessionManagerForTomcat-1.0.0.jar /usr/share/tomcat7/lib/
ライブラリを読み込み、セッションを保存するDynamoDBのテーブルを指定します。
$ sudo vi /usr/share/tomcat7/conf/context.xml <?xml version="1.0" encoding="UTF-8"?> <Context> <WatchedResource>WEB-INF/web.xml</WatchedResource> <Manager className="com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager" regionId="ap-northeast-1" endpoint="dynamodb.ap-northeast-1.amazonaws.com" createIfNotExist="true" /> </Context>
Tomcatをスタートしたら、セッションを使うURLにアクセスしてデータが保存されているか確認しましょう。
$ sudo service tomcat7 start
ブウラザからセッション管理がクラスタされているか確認
ELBを新規に作成して非スティッキーセッションにして動作確認します。ELB配下にセットアップ済みのEC2インスタンスを配備してIn Serviceになっていることを確認してからブラウザで動作確認してください。
セッション管理されている様子を確認するJSPは以下のコードです。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ page import="java.net.*" %> <% Integer counter = (Integer)session.getAttribute("counter"); if(counter == null){ counter = new Integer(1); }else{ counter = new Integer(counter.intValue()+1); } session.setAttribute("counter", counter); %> <%= session.getAttribute("counter") %><br> from Cluster 1<br> <%= InetAddress.getLocalHost() %><br> <%= session.getId() %><br>
ロードバランサーのアドレスを何度もリロードするとカウントアップしていきます。
しかし、、、、セッションIDは同じでうまく管理されているはずなのに値が上がったり下がったりしています。なぜ???
ここからが長い調査の始まりでした。。。。
Tomcatアーキテクチャーの仕組み
Tomcatは階層化されたコンテナによって管理されています。{TOMCAT_HOME}/conf/server.xmlの階層と同じです。Server > Service > Engine > Host の順番に設定されています。Hostの下にContextが設定されます。DynamoDBSessionManagerについては、このContext内のManager実装クラスとして設定していました。
さて、このContextの設定パラメータを眺めてみますと、気になる属性名があります。backgroundProcessorDelayです。子コンテナの処理を遅延させる値と書いてあります。特に設定しない場合には、親コンテナの設定を引き継ぐと書いてあります。
親であるHostコンテナにも同様に書いてありますので、その親であるEngineコンテナを見てみました。デフォルトで、じゅ、10秒遅延!これでラウンドロビン振り分けしたセッション管理の値がリアルタイムに同期されていないことが分かりましたね。
永続化を行うPersistentManagerを調べる
Tomcatがセッションの永続化を行う際に出てくるクラスがPersistentManagerクラスです。DynamoDBでセッション管理を行うDynamoDBSessionManagerクラスは、PersistentManagerBaseクラスを継承しています。とうことで、多くの設定についてPersistentManagerと同じになります。Tomcatのドキュメントを読むと以下のような属性の設定について記述がありました。
maxIdleBackupの属性値の説明では、セッションを永続化するときに必要な秒数が書いてあります。
maxIdleBackup + processExpiresFrequency * engine.backgroundProcessorDelay
processExpiresFrequencyの説明も読んでみます。セッション切れ頻度を表しているようです。デフォルト値が6で最小値が1だそうです。
これらから、セッション永続化を行うために遅延する時間が少なくともデフォルトで60秒以上であることが分かります。また、最小値に設定をしたとしても、1秒以上になってしまうため、ユーザーリクエストがラウンドロビンの状態で連打した場合にはセッション情報の管理に不整合が起きてしまいます。
以上により、Tomcatでセッションクラスターする時はスティッキーセッションにしてください。また、saveOnRestartをtrueにすることで、サーバーに障害が発生した際にセッション情報を即座に永続化してくれます。これにより、ロードバランサーが他のサーバーに振り分け直した場合にも、永続化された場所からセッション情報を復元するため、ユーザーがログアウトしてしまうことがありません。
Tomcatクラスタでセッションをフェイルオーバーする
さて、いろいろと調べてきましたので、ここで実際の動作を確認したいと思います。ELBのアドレスをブラウザで表示すると、Cluster1と表示されます。リロードすると表示がカウントアップされていきます。スティッキーセッションですので同じサーバーにリクエストが振られ続けています。
$ sudo vi /usr/share/tomcat7/conf/context.xml <?xml version="1.0" encoding="UTF-8"?> <Context backgroundProcessorDelay="1" > <WatchedResource>WEB-INF/web.xml</WatchedResource> <Manager className="com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager" regionId="ap-northeast-1" endpoint="dynamodb.ap-northeast-1.amazonaws.com" createIfNotExist="true" saveOnRestart="true" processExpiresFrequency="3" /> </Context>
ブラウザの結果です。
次にCluster1のサーバーのTomcatを止めてみます。すると、Cluster1のTomcatはDynamoDBにセッションデータを書き込みます。
ブラウザから改めてアクセスをすると、新しいサーバーは見たことがないセッションIDということでDynamoDBに確認に行きます。そして該当するデータがあればロードします。これにより、ブラウザでは同じセッションIDで、かつ、データを引き継いでいることを確認できます。
良い感じですね。Auto Scalingにおけるスケールインをした場合にもセッション情報を引き継げるはずです。
AWS Elastic BeanstalkでDynamoDBSessionManagerを使う
次にElastic Beanstalkを使ってセットアップしましょう。Beanstalkには拡張ポイントがありまして、設定ファイルを記述することができます。最新のEclipseプラグインにアップデートすることでDynamoDBによるセッション管理をある程度自動化してくれます。Eclipseから新規でAWS Java Web Projectを作成します。
このままでは.ebextensionsフォルダが見えませんので、Customize Viewをします。「.*」のチェックを外します。
.ebextensionsフォルダが見えましたね。
context.xmlは以前作成したものをコピペしてもらって構いません。アクセスキーやシークレットキーはPoserUserなIAMRoleが設定されているのであれば不要です。
*.configファイルは、Beanstalkセットアップ時に実行されるYAMLです。server-update.configというファイルが出来ていますね。ここでライブラリーのコピーと設定のコピーを行っています。
container_commands: copy-libraries: command: "cp WEB-INF/.ebextensions/*.jar /usr/share/tomcat7/lib/" replace-context: command: "cp WEB-INF/.ebextensions/context.xml /etc/tomcat7/context.xml"
デプロイをした後にスティッキーセッションの指定を忘れずに。ということで簡単にセットアップできましたね。
まとめ
今回は、Tomcatのセッションデータを永続化するためにPersistentManager実装であるDynamoDBSessionManagerを用いました。また、この機能を用いることで、ロードバランサー配下のインスタンスに障害が発生した際に、セッションのフェイルオーバーが行われることも確認できました。この際、スティッキーセッション指定にすることが必要でした。さらに、これらの仕組みを用いて構築を省力化するために、BeanstalkとEclipseプラグインを用いました。今日はこれでおしまいです。さて次は何しようかな。Redisを使った永続化でもやってみましょうか。BeanstalkはYAMLで書いて、CloudFormationはJSONで書いて、EC2はcloud-initで書いて、OpeWorksはRubyで書いて、、、どこに何を書けば良いんだろうw。次回もお楽しみに〜
参考資料
Manage Tomcat Session State with Amazon DynamoDB
Release: 1.0.0 - First version of the Amazon DynamoDB Session Manager for Apache Tomcat
Apache Tomcat 7 More about the Cat
Tomcat 7: How to enable HTTP Session DataBase persistence for PostgreSQL and DB2
Apache Tomcat 7 - The Manager Component
Customizing and Configuring AWS Elastic Beanstalk Environments